package com.phiture.erp.produce.core.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import com.phiture.erp.basic.api.pojo.resp.ErpMaterialInfoResp;
import com.phiture.erp.basic.api.pojo.resp.ErpMaterialPlanResp;
import com.phiture.erp.basic.api.service.ErpMaterialApi;
import com.phiture.erp.basic.api.service.ErpMaterialPlanApi;
import com.phiture.erp.common.enums.ErpMaterialAttrEnum;
import com.phiture.erp.common.enums.ErpMaterialPlanModeEnum;
import com.phiture.erp.produce.common.enums.MrpReleaseStatusEnum;
import com.phiture.erp.produce.common.enums.MrpTaskDispatchTypeEnum;
import com.phiture.erp.produce.core.convert.ErpMaterialRequirementPlanConvertMapper;
import com.phiture.erp.produce.core.factory.MrpTaskDispatchFactory;
import com.phiture.erp.produce.core.pojo.dto.*;
import com.phiture.erp.produce.core.pojo.vo.ErpMaterialRequirementPlanItemVO;
import com.phiture.erp.produce.core.service.*;
import com.phiture.erp.produce.dal.entity.ErpProduceMainPlanDO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.phiture.erp.common.constants.ErrorCodeConstants.*;

/**
 * @author YH
 * @date 2025-05-08-22:08
 */

@Slf4j
@Service
@RequiredArgsConstructor
public class MrpCalculationServiceImpl implements MrpCalculationService {
    private final ErpProduceMainPlanService produceMainPlanService;
    private final ErpMaterialPlanApi materialPlanApi;
    private final ErpBomService erpBomService;
    private final BomService bomService;
    private final ErpMaterialRequirementPlanService materialRequirementPlanService;
    private final ErpMaterialApi materialApi;
    private final MrpTaskDispatchFactory mrpTaskDispatchFactory;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void materialMrpCalculation(MrpCalculationDTO dto) {
        if (dto == null || CollUtil.isEmpty(dto.getProduceMainPlanNoList())) {
            throw exception(MRP_CALCULATION_PARAM_NON_NULL);
        }
        Set<String> produceMainPlanNos = new HashSet<>(dto.getProduceMainPlanNoList());
        List<ErpProduceMainPlanDO> produceMainPlanList = getProduceMainPlanList(produceMainPlanNos);
        Set<Long> materialIdSet = produceMainPlanList.stream().map(ErpProduceMainPlanDO::getMaterialId).collect(Collectors.toSet());
        // 校验产品级：物料的计划类型
        checkMaterial(materialIdSet);
        // 校验产品级：物料对应的BOM清单
        Set<Long> selfMadePartsMaterialIds = produceMainPlanList.stream()
                .filter(item -> ErpMaterialAttrEnum.SELF_MADE_PARTS.getAttr().equals(item.getMaterialAttr()))
                .map(ErpProduceMainPlanDO::getMaterialId).collect(Collectors.toSet());
        checkMaterialBom(selfMadePartsMaterialIds);
        // 获取物料基本信息
        Map<Long, ErpMaterialInfoResp> materialMap = materialApi.getMaterialMap(materialIdSet);
        // 产品级：物料MRP运算
        for (ErpProduceMainPlanDO planDO : produceMainPlanList) {
            ErpCreateMaterialRequirementPlanDTO planDTO = new ErpCreateMaterialRequirementPlanDTO();
            planDTO.setSourceBillId(planDO.getId()).setSourceBillNo(planDO.getNo());
            ErpMaterialRequirementsDTO requirementsDTO = processErpMaterialRequirementsDTO(planDO, materialMap);
            LinkedList<ErpCreateMaterialRequirementPlanItemsDTO> items = new LinkedList<>();
            Set<Long> processedMaterialIds = new HashSet<>();
            materialMrpCalculation(requirementsDTO, planDO.getEndDate(), items, processedMaterialIds);
            // 将产品级物料也添加到需求计划中
            items.addFirst(ErpMaterialRequirementPlanConvertMapper.INSTANCE
                    .toErpCreateMaterialRequirementPlanItemsDTO(requirementsDTO));
            planDTO.setItems(items);
            materialRequirementPlanService.createMaterialRequirementPlan(planDTO);
        }
        // 回填主计划Mrp计算标志
        produceMainPlanService.backfillMrpSelected(produceMainPlanNos);
    }

    @Override
    public void materialMrpTaskDispatch(List<Long> ids, boolean batchMode) {
        if(CollUtil.isEmpty(ids)){
            log.error("[MrpCalculationService.materialMrpTaskDispatch] 下达任务失败，参数为空");
            throw exception(MRP_RELEASED_FAIL_PARAM_ERROR);
        }

        HashSet<Long> materialRequirementIds = new HashSet<>(ids);
        List<ErpMaterialRequirementPlanItemVO> list = materialRequirementPlanService.getItemInfo(materialRequirementIds);
        if (CollUtil.isEmpty(list)) {
            throw exception(MRP_RELEASED_FAIL_INFO_ERROR);
        }

        Set<Long> selfMadePartsItemIds = new HashSet<>(list.size());
        Set<Long> purchasedPartsItemIds = new HashSet<>(list.size());
        list.stream()
                .filter(item -> MrpReleaseStatusEnum.UNRELEASED.getStatus().equals(item.getStatus()))
                .forEach(item -> {
                    if (ErpMaterialAttrEnum.isSelfMadeParts(item.getMaterialAttr())) {
                        selfMadePartsItemIds.add(item.getId());
                    } else if (ErpMaterialAttrEnum.isPurchasedParts(item.getMaterialAttr())) {
                        purchasedPartsItemIds.add(item.getId());
                    }
                });
        CompletableFuture.allOf(
                processTasksAsync(MrpTaskDispatchTypeEnum.MRP_PRODUCE_TASK, selfMadePartsItemIds, batchMode),
                processTasksAsync(MrpTaskDispatchTypeEnum.MRP_PURCHASE_TASK, purchasedPartsItemIds, batchMode)
        ).handle((result, ex) -> {
            if (ex != null) {
                log.error("MRP任务下达失败", ex);
                throw exception(MRP_RELEASED_FAIL_TASK_ERROR);
            }
            materialRequirementPlanService.changeIssuedStatus(materialRequirementIds);
            return null;
        }).join();
    }

    private CompletableFuture<Void> processTasksAsync(MrpTaskDispatchTypeEnum taskType, Set<Long> itemIds, boolean batchMode) {
        if (CollUtil.isEmpty(itemIds)) {
            return CompletableFuture.completedFuture(null);
        }

        return CompletableFuture.runAsync(() -> mrpTaskDispatchFactory.executeTask(taskType.getType(), MrpTaskDispatchDTO.of(itemIds, batchMode)));
    }

    /*
     * 产品级开工日期=主计划开工日期
     * 产品级完工日期=主计划完工日期
     */
    private ErpMaterialRequirementsDTO processErpMaterialRequirementsDTO(ErpProduceMainPlanDO planDO, Map<Long, ErpMaterialInfoResp> materialMap) {
        ErpMaterialInfoResp material = materialMap.get(planDO.getMaterialId());
        return ErpMaterialRequirementsDTO.of(
                material.getId(), material.getBarCode(), material.getName(),
                material.getUnitId(), material.getUnitName(), material.getStandard(),
                planDO.getMaterialAttr(), planDO.getQty(), planDO.getStartDate(), planDO.getEndDate()
        );
    }

    /**
     * 物料级MRP运算
     */
    protected void materialMrpCalculation(ErpMaterialRequirementsDTO requirementsDTO, LocalDate parentCompletionDate, LinkedList<ErpCreateMaterialRequirementPlanItemsDTO> items, Set<Long> processedMaterialIds) {
        // 检查物料是否已经处理过
        if (processedMaterialIds.contains(requirementsDTO.getMaterialId())) {
            return;
        }
        // 标记物料为已处理
        processedMaterialIds.add(requirementsDTO.getMaterialId());

        // 获取当前产品的BOM清单
        List<MrpBomInfoDTO> bomList = bomService.getBom(requirementsDTO.getMaterialId());
        if (CollUtil.isEmpty(bomList)) {
            // 外购件没有BOM清单
//            ErpCreateMaterialRequirementPlanItemsDTO itemsDTO = ErpMaterialRequirementPlanConvertMapper.INSTANCE.toErpCreateMaterialRequirementPlanItemsDTO(requirementsDTO);
//            items.add(itemsDTO);
            // 只有当物料未被处理过时才添加到列表
            if (!processedMaterialIds.contains(requirementsDTO.getMaterialId())) {
                items.add(ErpMaterialRequirementPlanConvertMapper.INSTANCE.toErpCreateMaterialRequirementPlanItemsDTO(requirementsDTO));
            }
            return;
        }
        for (MrpBomInfoDTO dto : bomList) {
            // 计算当前物料的开工日期和完工日期
            LocalDate startDate = parentCompletionDate.minusDays(dto.getLeadTime());
            int productionLeadTime = ErpMaterialAttrEnum.isSelfMadeParts(dto.getMaterialAttr()) ? dto.getProduceLeadTime() : dto.getRecevingLeadTime();
            LocalDate endDate = startDate.plusDays(productionLeadTime);
            // 计算数量
            BigDecimal qty = requirementsDTO.getPlanQuantity().multiply(dto.getConsumeFixQty()).setScale(2, RoundingMode.HALF_UP);
            ErpMaterialRequirementsDTO childRequirements = ErpMaterialRequirementsDTO.of(
                    dto.getMaterialId(), dto.getMaterialBarCode(), dto.getMaterialName(),
                    dto.getMaterialUnitId(), dto.getMaterialUnitName(), dto.getMaterialStandard(),
                    dto.getMaterialAttr(), qty, startDate, endDate
            );
            items.add(ErpMaterialRequirementPlanConvertMapper.INSTANCE.toErpCreateMaterialRequirementPlanItemsDTO(childRequirements));
            materialMrpCalculation(childRequirements, endDate, items, processedMaterialIds);
        }
    }

    /**
     * 获取主计划信息
     */
    private List<ErpProduceMainPlanDO> getProduceMainPlanList(Collection<String> produceMainPlanNos) {
        List<ErpProduceMainPlanDO> produceMainPlanList = produceMainPlanService.getListByNos(produceMainPlanNos);
        if (CollUtil.isEmpty(produceMainPlanList)) {
            log.error("[BaseMrpCalculationHandler.getProduceMainPlanList] 未查询到主计划信息，produceMainPlanNos:{}", JsonUtils.toJsonString(produceMainPlanNos));
            throw exception(MRP_CALCULATION_MAIN_PLAN_ERROR);
        }
        return produceMainPlanList;
    }

    /**
     * 校验物料计划类型为'MRP'
     */
    private void checkMaterial(Collection<Long> materialIdSet) {
        List<ErpMaterialPlanResp> materialPlanList = materialPlanApi.getMaterialPlanInfo(materialIdSet);
        if (CollUtil.isEmpty(materialPlanList)) {
            throw exception(MRP_CALCULATION_MATERIAL_INFO_ERROR);
        }

        boolean match = materialPlanList.stream().allMatch(item -> ErpMaterialPlanModeEnum.MRP.getMode().equals(item.getPlanningMode()));
        if (!match) {
            log.error("[BaseMrpCalculationHandler.checkMaterialPlanTypeAndAttr] MRP运算失败，物料的计划类型信息有误, materialPlanList:{}", JsonUtils.toJsonString(materialPlanList));
            throw exception(MRP_CALCULATION_MATERIAL_INFO_ERROR);
        }
    }

    /**
     * 校验物料是否没有对应的BOM
     */
    private void checkMaterialBom(Collection<Long> materialIds) {
        if (!erpBomService.checkMaterialHasBom(materialIds)) {
            throw exception(MRP_CALCULATION_MATERIAL_BOM_NOT_EXISTS);
        }
    }
}
